

import java.io.*;

import java.net.*;

import java.util.Random;



/*

 * @author Harshit Krishna

 */



public class TCPServer implements Runnable {



	//payloads

	static String handshakePeerID=null;

	static String index=null;

	static String bitfield=null;

	

	static int count;

	String Filename;

	String New_Dir;

	String Filename_search;

	int FileSize;

	int PieceSize;

	int Num_of_pieces; // Filesize / Piecesize



	String Read_File_Parts = "peer_" + peerProcess.currentPeerId;

	String clientSentence;

	String CapitalizedSentence;

	String ServerID = peerProcess.currentPeerId;

	int PORT_NO = 6010;

	// change header and port no and other parameters

	String Header = "HEADER" + ServerID;

	String HeaderFromClient;

	String PeerIDcheck;

	ServerSocket welcomeSocket;

	String SentHeader;

	String Message_From_Client;

	String Message_Type = "";

	long CHOKELASTCHECKTIME;

	long startTime = 0, endTime = 0;

	private static Object Lock;



	ConfigReader CFgReader = new ConfigReader();



	// Socket connectionSocket = welcomeSocket.accept();



	public TCPServer(ServerSocket serverSocket) {



		Filename = CFgReader.fileName;

		New_Dir = "peer_" + peerProcess.currentPeerId;

		Filename_search = New_Dir + "/tempsplit";



		FileSize = CFgReader.fileSize;

		PieceSize = CFgReader.pieceSize;

		Num_of_pieces = peerProcess.no_of_pieces;



		welcomeSocket = serverSocket;



		Lock = new Object();

	}



	public void run() {

		try {

			

			System.out.println("inside tcp server");

			

			// ConfigReader CFgReader=new ConfigReader();

			while (true) {



				Socket connectionSocket = welcomeSocket.accept();

				BufferedReader inFromClient = new BufferedReader(

						new InputStreamReader(connectionSocket.getInputStream()));



				DataOutputStream outToClient = new DataOutputStream(

						connectionSocket.getOutputStream());

				HeaderFromClient = inFromClient.readLine();

				PeerIDcheck = HeaderFromClient.substring(28, 32);



				int UpdateBitfield = findIndex(PeerIDcheck);

				// write log file over here

				// WriteToLog("Peer " + peerProcess.currentPeerId +

				// " is connected to " + PeerIDcheck);



				SentHeader = Header + '\n';



				outToClient.writeBytes(SentHeader);



				// SEtting the Bit field value here

				SentBitField(outToClient, MainServer.Bitfield);



				String Message_Type = "";

				boolean CHOKE = false;

				CHOKELASTCHECKTIME = System.currentTimeMillis();

				startTime = 0;

				endTime = 0;



				while (true) {



					if (!CHOKE) {



						ObjectInputStream ois1 = new ObjectInputStream(

								connectionSocket.getInputStream());

						byte[] buffer = (byte[]) ois1.readObject();

						

						System.out.println(buffer.toString());

						

						String buffer2string = new String(buffer);

						Message_Type = parse(buffer);

						if (Message_Type.endsWith("Not Interested")) {

							// WriteToLog("Peer " + peerProcess.currentPeerId +

							// " recieved 'not interested' message from client "

							// + PeerIDcheck);



							peerProcess.peerinfo[findIndex(PeerIDcheck)].IsInterested = false;

						} else if (Message_Type.endsWith("Interested")) {

							// System.out.println("Client is interested");

							// WriteToLog("Peer " + peerProcess.currentPeerId +

							// " recieved 'interested' message from client " +

							// PeerIDcheck);

							peerProcess.peerinfo[findIndex(PeerIDcheck)].IsInterested = true;

						} else if (Message_Type.endsWith("Requested"))



						{

							processRequested(buffer, connectionSocket);



						} else if (Message_Type.endsWith("Have Message")) {

							processHaveMessage(buffer, connectionSocket, CHOKE);

						} else if (Message_Type.endsWith("End")) {



							peerProcess.peerinfo[findIndex(PeerIDcheck)].IsRunning = false;



							connectionSocket.close();

							break;



						} else if (Message_Type.endsWith("BitFieldUpdate")) {

							// Sending Server Bitfield info

							processBitField(connectionSocket);

						}

					} else {

						// preferred neighbor and optimistic choking

						long currenttime = System.currentTimeMillis();



						// LOGIC FOR NORMAL UNCHOKE

						if (((currenttime - CHOKELASTCHECKTIME) > (CFgReader.unchokingInt * 1000))

								&& peerProcess.peerinfo[findIndex(PeerIDcheck)].IsRunning) {



							if (ServerTimeRank(PeerIDcheck, true) <= CFgReader.numPreferredNeighbors) {

								// Sent UNCHOKE



								CHOKE = false;

								peerProcess.peerinfo[findIndex(PeerIDcheck)].IsChoked = false;

								// WriteToLog("Peer " + PeerIDcheck +

								// " is unchoked by " +

								// peerProcess.currentPeerId);



								ObjectOutputStream oos2 = new ObjectOutputStream(

										connectionSocket.getOutputStream());

								byte[] chokeMsgByte = new byte[5];

								chokeMsgByte[0] = (byte) 0;

								chokeMsgByte[1] = (byte) 0;

								chokeMsgByte[2] = (byte) 0;

								chokeMsgByte[3] = (byte) 1;

								chokeMsgByte[4] = (byte) 1;

								oos2.writeObject(chokeMsgByte);



								PrintPreferredNeighbours();

							}



							CHOKELASTCHECKTIME = currenttime;

						}



						synchronized (Lock) {



							currenttime = System.currentTimeMillis();

							// LOGIC FOR OPTIMISTIC UNCHOKE

							if (((currenttime - peerProcess.optimisticUnchokeInterval) > (CFgReader.optimisticUnchokingInt * 1000))

									&& peerProcess.peerinfo[findIndex(PeerIDcheck)].IsRunning) {



								if (peerProcess.peerinfo[findIndex(PeerIDcheck)].IsChoked == true

										&& peerProcess.peerinfo[findIndex(PeerIDcheck)].IsInterested == true) {

									// Sent UNCHOKE



									CHOKE = false;

									peerProcess.peerinfo[findIndex(PeerIDcheck)].IsChoked = false;

									// WriteToLog("Peer " +

									// peerProcess.currentPeerId +

									// " has optimistically unchoked neighbor "

									// + PeerIDcheck);



									ObjectOutputStream oos2 = new ObjectOutputStream(

											connectionSocket.getOutputStream());

									byte[] chokeMsgByte = new byte[5];

									chokeMsgByte[0] = (byte) 0;

									chokeMsgByte[1] = (byte) 0;

									chokeMsgByte[2] = (byte) 0;

									chokeMsgByte[3] = (byte) 1;

									chokeMsgByte[4] = (byte) 1;

									oos2.writeObject(chokeMsgByte);

								}

								peerProcess.optimisticUnchokeInterval = System.currentTimeMillis();

							}

						}

					}

				}

			}



		} catch (Exception ex) {

			System.out.println("SERVER ERROR - C0" + ex.toString());

		}



	}



	// although i am passing socket connection in these function, i am skeptic

	// that it will create a new object of socket and might pop in ambiguity

	// in that case make it public static



	// function for REQUESTED

	public void processRequested(byte[] tempBuffer, Socket tempConnectionSocket)

			throws IOException



	{

		ObjectOutputStream outputStream1 = new ObjectOutputStream(

				tempConnectionSocket.getOutputStream());

		ObjectOutputStream outputStream2 = new ObjectOutputStream(

				tempConnectionSocket.getOutputStream());

		Message_From_Client = new String(tempBuffer);

		String file_req = "";

		int loc;

		for (loc = 0; loc < 4; loc++) {

			if (Message_From_Client.substring(5 + loc).startsWith("0")) {

				continue;

			} else {

				break;

			}

		}



		if (loc == 4) {

			file_req = "0";

		} else {

			file_req = Message_From_Client.substring(5 + loc);

		}

		// WriteToLog("Peer " + peerProcess.currentPeerId +

		// " recieved 'requested' message from client " + PeerIDcheck +

		// " piece no " + file_req);



		// startTime = System.currentTimeMillis();



		FileInputStream fis = new FileInputStream(Filename_search + file_req);

		byte[] buffer1 = new byte[fis.available() + 2];

		fis.read(buffer1, 2, fis.available());

		buffer1[0] = (byte) 9;

		buffer1[1] = (byte) 9;



		outputStream1.writeObject(buffer1);



		byte[] bitFieldByte = new byte[Num_of_pieces + 2];

		bitFieldByte[0] = (byte) 6;

		bitFieldByte[1] = (byte) 6;

		for (int i = 2; i < Num_of_pieces + 2; i++) {

			bitFieldByte[i] = (byte) MainServer.Bitfield.charAt(i - 2);

		}

		outputStream2.writeObject(bitFieldByte);

	}



	// remember to check tempChoke

	public void processHaveMessage(byte[] tempBuffer,

			Socket tempConnectionSocket, boolean tempChoke) throws IOException {

		Message_From_Client = new String(tempBuffer);



		String Have_Loc = "";

		int havloc;

		for (havloc = 0; havloc < 4; havloc++) {

			if (Message_From_Client.substring(5 + havloc).startsWith("0")) {

				continue;

			} else {

				break;

			}

		}



		if (havloc == 4) {

			Have_Loc = "0";

		} else {

			Have_Loc = Message_From_Client.substring(5 + havloc);

		}



		//

		// WriteToLog("Peer " + peerProcess.currentPeerId +

		// " recieved 'have' message from client " + PeerIDcheck +

		// " for piece no. " + Have_Loc);



		endTime = System.currentTimeMillis();



		peerProcess.peerinfo[findIndex(PeerIDcheck)].LinkSpeed = endTime

				- startTime;



		// synchronized (Lock) {

		// LOGIC FOR CHOKE

		long currenttime = System.currentTimeMillis();



		if (currenttime - CHOKELASTCHECKTIME > (CFgReader.unchokingInt * 1000)) {

			if (ServerTimeRank(PeerIDcheck, false) > CFgReader.numPreferredNeighbors) {

				tempChoke = true;

				peerProcess.peerinfo[findIndex(PeerIDcheck)].IsChoked = true;

				// WriteToLog("Peer " + PeerIDcheck + " is choked by " +

				// peerProcess.currentPeerId);



				peerProcess.optimisticUnchokeInterval = System

						.currentTimeMillis();



				PrintPreferredNeighbours();



			}

			CHOKELASTCHECKTIME = currenttime;

		}



		ObjectOutputStream oos2 = new ObjectOutputStream(

				tempConnectionSocket.getOutputStream());

		byte[] chokeMsgByte = new byte[6];

		chokeMsgByte[0] = (byte) 0;

		chokeMsgByte[1] = (byte) 0;

		chokeMsgByte[2] = (byte) 0;

		chokeMsgByte[3] = (byte) 1;

		chokeMsgByte[4] = (byte) 0;

		if (tempChoke) {

			chokeMsgByte[5] = (byte) 1; // Choking the peer



		} else {

			chokeMsgByte[5] = (byte) 0; // Not Choking the peer



		}

		oos2.writeObject(chokeMsgByte);



	}



	public void processBitField(Socket tempConnectionSocket) throws IOException {



		byte[] bitFieldByte = new byte[Num_of_pieces + 2];

		bitFieldByte[0] = (byte) 6;

		bitFieldByte[1] = (byte) 6;

		for (int i = 2; i < Num_of_pieces + 2; i++) {

			bitFieldByte[i] = (byte) MainServer.Bitfield.charAt(i - 2);

		}

		ObjectOutputStream outputStream1 = new ObjectOutputStream(

				tempConnectionSocket.getOutputStream());

		outputStream1.writeObject(bitFieldByte);



	}



	public void SentBitField(DataOutputStream outToClient, String bitfield) {

		try {



			String bitPayload = "";

			String Sent_Bitfield = "";

			for (int t = 0; t < bitfield.length(); t++) {

				bitPayload = bitPayload + bitfield.charAt(t); // Bitfield

																// Payload Set

																// here in this

																// loop



			}

			String Messagetype;

			Messagetype = "5"; // Bit field Message type



			String Messagewl = Messagetype + bitPayload;

			int messagelength = Messagewl.length();

			// System.out.println("Length of bitfield1 " + messagelength);

			// String inbyte=""+messagelength;

			String inbyte = Integer.toString(messagelength);



			int byte4 = inbyte.length();

			// System.out.println("Length of bitfield2 " + byte4);

			String combitmess = "";

			if (byte4 == 1) {

				String lenbit = Integer.toString(messagelength);

				combitmess = "000" + lenbit + Messagewl;

			} else if (byte4 == 2) {

				String lenbit = Integer.toString(messagelength);

				combitmess = "00" + lenbit + Messagewl;



			} else if (byte4 == 3) {

				String lenbit = Integer.toString(messagelength);

				combitmess = "0" + lenbit + Messagewl;



			} else {

				String lenbit = Integer.toString(messagelength);

				combitmess = lenbit + Messagewl;

			}



			Sent_Bitfield = combitmess + '\n';

			outToClient.writeBytes(Sent_Bitfield);



		} catch (Exception e) {

			// System.out.println("Sent Bitfield" + e.toString());

		}

	}



	// function to retrun back parse meesage after decoding the message type

	

	public int ServerTimeRank(String PeerID, boolean unchoke) {

		int rank = 1;

		try {

			synchronized (Lock) {

				int Peer_id = findIndex(PeerID);



				for (int i = 0; i < peerProcess.no_of_peers; i++) {

					// System.out.println("peerProcess.peerinfo[i].LinkSpeed>" +

					// findIndex(PeerID) + " " +

					// peerProcess.peerinfo[i].LinkSpeed);

					if (peerProcess.peerinfo[Peer_id].LinkSpeed > peerProcess.peerinfo[i].LinkSpeed

							&& peerProcess.peerinfo[i].LinkSpeed != 0) {

						if (peerProcess.peerinfo[Peer_id].IsChoked == unchoke)

							rank++;

					}

				}

			}



		} catch (Exception e) {

			// System.out.println("STR Error : " + e.toString());

		}



		return rank;

	}



	public void PrintPreferredNeighbours() {



		String peerId = "";

		if (System.currentTimeMillis() - peerProcess.peferredNeighborTime > (CFgReader.unchokingInt * 1000)) {



			for (int i = 0; i < peerProcess.no_of_peers; i++) {

				if (peerProcess.peerinfo[i].IsChoked == false

						&& peerProcess.peerinfo[i].LinkSpeed != 0) {

					peerId += peerProcess.peerinfo[i].PeerID + ", ";

				}

			}



			peerProcess.peferredNeighborTime = System.currentTimeMillis();

			if (peerId.length() != 0) {// WriteToLog("Peer " +

										// peerProcess.currentPeerId +

										// " has the preferred neighbors " +

										// peerId.substring(0,

										// peerId.length()-2));

			}



		}



	}



	//messageParser

	public static String parse(byte[] packet)

	{

		int length = packet.length;

		String packetString = new String(packet);

		String type = null;

		//payloads



		//////

		String len;

		byte[] piece;

		

		if(packetString.substring(0, 3).equals("CNT"))

		{

			type = "handshaking";

			handshakePeerID = packetString.substring(28, length);

		}

		else if(packetString.substring(4,5).equals("0"))

		{

			// no payload

			type = "choke";

		}

		else if(packetString.substring(4,5).equals("1"))

		{

			// no payload

			type = "unchoke";

		}

		else if(packetString.substring(4,5).equals("2"))

		{

			// no payload

			type = "interested";

		}

		else if(packetString.substring(4,5).equals("3"))

		{

			// no payload

			type = "not interested";

		}

		else if(packetString.substring(4,5).equals("4"))

		{

			type = "have";

			index = packetString.substring(5,length);

		}

		else if(packetString.substring(4,5).equals("5"))

		{

			type = "bitfield";

			bitfield = packetString.substring(5,length);

		}

		else if(packetString.substring(4,5).equals("6"))

		{

			type = "request";

			index = packetString.substring(5,length);

		}

		else if(packetString.substring(4,5).equals("7"))

		{

			type = "piece";

			

			// get length field from packet

			len = packetString.substring(0,4);

			int msgLen = Integer.parseInt(len);

			

			System.out.println("msgLen: " + msgLen); 

			

			// get piece size

			ConfigReader cfg = new ConfigReader();

			cfg.readCommon();

			int pieceSize = cfg.pieceSize;

			String fileName = cfg.fileName;

			int fileSize = cfg.fileSize;

			int indexSize = length - 5 - pieceSize;

			

			// check to see if this is the last piece (the pieceSize will be less than expected)

			if(length < pieceSize+5)

			{

				System.out.println("must be last piece");

				

				// calculate actual piece size by computing the size of the index

				int numPieces = (fileSize + pieceSize - 1) / pieceSize;

				String numPiecesString = Integer.toString(numPieces);

				indexSize = numPiecesString.length(); 

				

				pieceSize = length - 5 - indexSize;

				

				System.out.println("actual piece size: " + pieceSize);

			}

			

			// put the file piece into piece byte array

			piece = new byte[pieceSize];

			index = packetString.substring(5,(5+indexSize));

			int j = 0;

			for(int i = 5+indexSize; i < length; i++)

			{

				piece[j] = packet[i];

				j++;

			}

			

			// write the piece to the properly named file

			String newFile = "peer_" + peerProcess.peerID + "/" + fileName + index;

	    	

	    	System.out.println("File part: " + newFile);

	    	

	    	FileOutputStream fos;

			try

			{

				fos = new FileOutputStream(newFile);

				fos.write(piece, 0, pieceSize);

				

				// flush and close the file output stream

		    	fos.flush();

		    	fos.close();

			} 

			catch (FileNotFoundException e)

			{

				e.printStackTrace();

			} 

			catch (IOException e)

			{

				e.printStackTrace();

			}

		}

		

		return type;

	}

	

	

	

	public int findIndex(String SearchThis) {



		try {



			for (int i = 0; i < peerProcess.no_of_peers; i++) {

				if (peerProcess.peerinfo[i].PeerID.endsWith(SearchThis)) {

					return i;

				}



			}

		} catch (Exception e) {

			 System.out.println("Exception" + e.toString());

		}

		return -1;

	}



}

